package com.kweative.lingua.server.structures;

import com.kweative.lingua.server.Application;
import com.kweative.lingua.server.Config;
import com.kweative.lingua.server.Controller;
import com.kweative.lingua.server.annotations.MethodInfo;
import com.kweative.lingua.server.exceptions.NotFoundException;
import com.kweative.lingua.server.request.RequestHandler;
import com.kweative.lingua.server.response.ResponseHandler;
import com.kweative.lingua.server.session.Session;
import com.kweative.type.Node;
import com.kweative.type.Node.Type;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author henrik
 */
public class AppRequest {
    private Application application;
    private Controller<Application> controller;
    private Method method;
    private RequestHandler requestHandler;
    private Arguments arguments = null;
    private String SID = "";
    private Session session;
    private String transationId;
    private String applicationName;
    private String controllerName;
    private String methodName;
    private boolean resolved = false;

    public String getTransactionId() {
        return transationId;
    }

    public void setTransationId(String transationId) {
        this.transationId = transationId;
    }

    public boolean isResolved() {
        return resolved;
    }

    public String getSID() {
        return SID;
    }

    public void setSID(String SID) {
        this.SID = SID;
    }

    

    public Application getApplication() {
        return application;
    }

    public Controller<Application> getController() {
        return controller;
    }

    public Method getMethod() {
        return method;
    }

    public Arguments getArguments() {
        return arguments;
    }

    public void setArguments(Arguments arguments) {
        this.arguments = arguments;
    }
    
    public void resolve() throws NotFoundException {
        if (resolved) return;
        
        application = Config.instance().getApplication(applicationName);
        if (application == null)
            throw new NotFoundException("Application not found:" + applicationName);

        //Handle Session

        if (SID != null && !SID.isEmpty()) {
            session = application.getSession(SID);
            if (session != null)
                SID = session.getSID();
            else
                SID = "";
        }
        if (SID == null || SID.isEmpty()) {
            session = application.newSession();
            SID = session.getSID();
        }

        try {
            controller = application.getController(controllerName);
        } catch (Exception ex) {
            throw new NotFoundException("Controller not found:" + controllerName);
        }
        //Resolve method
        Method methodTest = null;

        Method[] methods = controller.getClass().getDeclaredMethods();
        boolean found = false;
        MethodInfo a = null;
        Arguments args = null;
        for(Method m: methods) {
            if (!m.getName().equalsIgnoreCase(methodName))
                continue;
            methodTest = m;
            Class<?>[] parmTypes = m.getParameterTypes();
            
            args = (Arguments) arguments.clone();

            //Check if last parm is Session - and add parm if so
            if (parmTypes.length > 0 && parmTypes[parmTypes.length-1].equals(Session.class)) {
                Node sesNode = new Node(Config.SESSIONIDNAME,Type.NAMEVALUE);
                sesNode.setValue(session);

                if (args instanceof ArgumentsNamed) {
                    ((ArgumentsNamed)args).add(Config.SESSIONIDNAME,sesNode);
                } else {
                    args.add(sesNode);
                }
            }

            if (parmTypes.length != args.size())
                continue;

            if (args.size() > 0) {
                a = m.getAnnotation(MethodInfo.class);
                if (a != null && args instanceof ArgumentsNamed) {
                    
                    if (a.nonpublic()) continue;

                    boolean foundAll = true;
                    for(String p:a.parms()) {
                        if (((ArgumentsNamed)args).has(p)) continue;
                        foundAll = false;
                    }

                    if (!foundAll) continue;
                } else {
                    boolean succeeded = true;
                    for(int i = 0; i < args.size();i++) {
                        try {
                            parmTypes[i].cast(args.get(i).getValue());
                        } catch(ClassCastException ex) {
                            succeeded = false;
                            break;
                        }
                    }
                    if (!succeeded) {
                        continue;
                    }
                }
            }

            found = true;
            break;
        }
        if (!found && methodTest != null) {
            throw new NotFoundException("Mismatch of arguments for method "+methodName+":" + methodTest.toGenericString() + "\n" + ((a != null) ? a.toString() : ""));
        }
        if (methodTest == null)
            throw new NotFoundException("Unknown method: "+controllerName+"."+methodName);
        arguments = args;
        method = methodTest;
        resolved = true;
    }
    public Node execute(ResponseHandler rh) throws Throwable {
        resolve();
        rh.setSession(session);
        
        Node response = new Node("response");
        try {
            String resultName = null;
            Object returnValue = null;
            Node result = null;

            MethodInfo a = method.getAnnotation(MethodInfo.class);
            

            if (a != null && !a.result().isEmpty()) {
                resultName = a.result();

            }
            result = new Node(resultName);

            Class<?>[] parms = method.getParameterTypes();
            Object[] args = new Object[parms.length];


            for(int i = 0;i < parms.length;i++) {
                Node child = null;
                if (a != null && a.parms().length > i && arguments instanceof ArgumentsNamed) {
                    //Use named parameters
                    child = ((ArgumentsNamed)arguments).get(a.parms()[i]);
                } else {
                    child = arguments.get(i);
                }

                Object value = child.toObject(parms[i]);
                
                args[i] = value;
            }

            returnValue = method.invoke(controller,args);
            
            if (returnValue != null) {
                if (returnValue instanceof Node) {
                    response = (Node) returnValue;
                } else if (returnValue instanceof FileWrapper) {
                    response.setType(Type.VALUE);
                    response.setValue(returnValue);
                } else {
                    result = Node.fromObject(returnValue);
                    response = result;
                }
            }

        } catch(InvocationTargetException ex) {
            throw ex.getCause();
        }
        return response;
    }

    public String getApplicationName() {
        return applicationName;
    }

    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }

    public String getControllerName() {
        return controllerName;
    }

    public void setControllerName(String controllerName) {
        this.controllerName = controllerName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }
    public boolean isDocumentiation() {
        return methodName.equals("describe") && controllerName.equals("_info");
    }

    public RequestHandler getRequestHandler() {
        return requestHandler;
    }

    public void setRequestHandler(RequestHandler requestHandler) {
        this.requestHandler = requestHandler;
    }
    

}
